﻿
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Security.Cryptography;
using Windows.Security.Cryptography.Core;
using Windows.Storage;
using Windows.Storage.Streams;

namespace mBook.Utils
{
    public class AESUtils
    {
        /// <summary>
        /// Encrypt a BASE64 encoded string of encrypted data, returns a plain string
        /// </summary>
        /// <param name="base64StringToDecrypt">an Aes encrypted AND base64 encoded string</param>
        /// <param name="passphrase">The passphrase.</param>
        public static void EncryptEpub(string inputFile, string outputFile, string passphrase)
        {
            ////Set up the encryption objects
            //using (AesCryptoServiceProvider acsp = new AesCryptoServiceProvider())
            //{
            //    acsp.Key = Encoding.UTF8.GetBytes(passphrase);

            //    acsp.GenerateIV();
            //    acsp.IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

            //    acsp.Mode = CipherMode.CBC;
            //    acsp.Padding = PaddingMode.PKCS7;

            //    FileStream inputFileStream = new FileStream(inputFile, FileMode.Open, FileAccess.Read);
            //    byte[] RawBytes = new byte[(int)inputFileStream.Length];
            //    inputFileStream.Read(RawBytes, 0, (int)inputFileStream.Length);

            //    inputFileStream.Close();

            //    ICryptoTransform ictD = acsp.CreateEncryptor();

            //    //Decrypt into stream
            //    MemoryStream msD = new MemoryStream(RawBytes, 0, RawBytes.Length);
            //    CryptoStream csD = new CryptoStream(msD, ictD, CryptoStreamMode.Write);
            //    //csD now contains original byte array, fully decrypted

            //    FileStream outputFileStream = new FileStream(outputFile, FileMode.Create, FileAccess.Write);
            //    int data;
            //    while ((data = csD.ReadByte()) != -1)
            //    {
            //        outputFileStream.WriteByte((byte)data);
            //    }

            //    msD.Close();
            //    csD.Clear();
            //    csD.Close();
            //    outputFileStream.Close();
            //}
        }

        /// <summary>
        /// Decrypts a BASE64 encoded string of encrypted data
        /// </summary>
        /// <param name="passphrase">The passphrase.</param>
        //public static byte[] DecryptEpubToBytes(string inputFile, string passphrase)
        //{
        //    //Set up the encryption objects
        //    using (AesCryptoServiceProvider acsp = new AesCryptoServiceProvider())
        //    {
        //        acsp.Key = Encoding.UTF8.GetBytes(passphrase);

        //        acsp.GenerateIV();
        //        acsp.IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

        //        acsp.Mode = CipherMode.CBC;
        //        acsp.Padding = PaddingMode.PKCS7;

        //        FileStream inputFileStream = new FileStream(inputFile, FileMode.Open, FileAccess.Read);
        //        byte[] RawBytes = new byte[(int)inputFileStream.Length];
        //        inputFileStream.Read(RawBytes, 0, (int)inputFileStream.Length);

        //        inputFileStream.Close();

        //        ICryptoTransform ictD = acsp.CreateDecryptor();

        //        //Decrypt into stream
        //        MemoryStream msD = new MemoryStream(RawBytes, 0, RawBytes.Length);
        //        CryptoStream csD = new CryptoStream(msD, ictD, CryptoStreamMode.Read);
        //        //csD now contains original byte array, fully decrypted

        //        byte[] byteString = new byte[msD.Length];
        //        int data, index = 0;
        //        while ((data = csD.ReadByte()) != -1)
        //        {
        //            byteString[index++] = (byte)data;
        //        }

        //        msD.Close();
        //        csD.Close();

        //        return byteString;
        //    }
        //}

        /// <summary>
        /// Decrypts a BASE64 encoded string of encrypted data
        /// </summary>
        /// <param name="passphrase">The passphrase.</param>
        //public static string DecryptEpubToString(string inputFile, string passphrase)
        //{
        //    //Set up the encryption objects
        //    using (AesCryptoServiceProvider acsp = new AesCryptoServiceProvider())
        //    {
        //        acsp.Key = Encoding.UTF8.GetBytes(passphrase);

        //        acsp.GenerateIV();
        //        acsp.IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

        //        acsp.Mode = CipherMode.CBC;
        //        acsp.Padding = PaddingMode.PKCS7;

        //        FileStream inputFileStream = new FileStream(inputFile, FileMode.Open, FileAccess.Read);
        //        byte[] RawBytes = new byte[(int)inputFileStream.Length];
        //        inputFileStream.Read(RawBytes, 0, (int)inputFileStream.Length);

        //        inputFileStream.Close();

        //        ICryptoTransform ictD = acsp.CreateDecryptor();

        //        //Decrypt into stream
        //        MemoryStream msD = new MemoryStream(RawBytes, 0, RawBytes.Length);
        //        CryptoStream csD = new CryptoStream(msD, ictD, CryptoStreamMode.Read);
        //        //csD now contains original byte array, fully decrypted
                
        //        StreamReader rd = new StreamReader(csD);                
        //        string strData = rd.ReadToEnd();

        //        msD.Close();
        //        rd.Close();
        //        csD.Close();

        //        return strData;
        //    }
        //}

        /// <summary>
        /// Decrypts a BASE64 encoded string of encrypted data
        /// </summary>
        /// <param name="passphrase">The passphrase.</param>
        public static void DecryptEpub(string inputFile, string outputFile, string passphrase)
        {
            ////Set up the encryption objects
            //using (AesCryptoServiceProvider acsp = new AesCryptoServiceProvider())
            //{
            //    acsp.Key = Encoding.UTF8.GetBytes(passphrase);

            //    acsp.GenerateIV();
            //    acsp.IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

            //    acsp.Mode = CipherMode.CBC;
            //    acsp.Padding = PaddingMode.PKCS7;

            //    FileStream inputFileStream = new FileStream(inputFile, FileMode.Open, FileAccess.Read);
            //    byte[] RawBytes = new byte[(int)inputFileStream.Length];
            //    inputFileStream.Read(RawBytes, 0, (int)inputFileStream.Length);

            //    inputFileStream.Close();

            //    ICryptoTransform ictD = acsp.CreateDecryptor();

            //    //Decrypt into stream
            //    MemoryStream msD = new MemoryStream(RawBytes, 0, RawBytes.Length);
            //    CryptoStream csD = new CryptoStream(msD, ictD, CryptoStreamMode.Read);
            //    //csD now contains original byte array, fully decrypted

            //    FileStream outputFileStream = new FileStream(outputFile, FileMode.Create, FileAccess.Write);
            //    int data;
            //    while ((data = csD.ReadByte()) != -1)
            //    {
            //        outputFileStream.WriteByte((byte)data);
            //    }

            //    File.SetAttributes(outputFile, FileAttributes.Hidden);
            //    File.SetAttributes(outputFile, FileAttributes.Temporary);

            //    msD.Close();
            //    csD.Clear();
            //    csD.Close();                
            //    outputFileStream.Close();
            //}
        }

        /// <summary>
        /// Decrypt a Media file with BASE64 encoded string of encrypted data
        /// </summary>
        /// <param name="inputFile"></param>
        /// <param name="outputFile"></param>
        /// <param name="passphrase"></param>
        /// <param name="encryptedLength"></param>
        public static void DecryptMedia(string inputFile, string outputFile, string passphrase, int encryptedLength)
        {
            ////Set up the encryption objects
            //using (AesCryptoServiceProvider acsp = new AesCryptoServiceProvider())
            //{
            //    acsp.Key = Encoding.UTF8.GetBytes(passphrase);

            //    acsp.GenerateIV();
            //    acsp.IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

            //    acsp.Mode = CipherMode.CBC;
            //    acsp.Padding = PaddingMode.PKCS7;

            //    FileStream inputFileStream = new FileStream(inputFile, FileMode.Open, FileAccess.Read);
            //    byte[] RawBytes = new byte[(int)inputFileStream.Length];
            //    inputFileStream.Read(RawBytes, 0, (int)inputFileStream.Length);

            //    inputFileStream.Close();

            //    ICryptoTransform ictD = acsp.CreateDecryptor();

            //    //Decrypt into stream
            //    MemoryStream msD = new MemoryStream(RawBytes, 0, RawBytes.Length);
            //    CryptoStream csD = new CryptoStream(msD, ictD, CryptoStreamMode.Read);
            //    //csD now contains original byte array, fully decrypted

            //    FileStream outputFileStream = new FileStream(outputFile, FileMode.Create, FileAccess.Write);
            //    int data;
            //    while (outputFileStream.Length < msD.Length - 16)
            //    {
            //        data = csD.ReadByte();
            //        outputFileStream.WriteByte((byte)data);
            //    }

            //    msD.Close();
            //    csD.Clear();
            //    csD.Close();
            //    outputFileStream.Close();
            //}
        }



        private static IBuffer GetMD5Hash(string key)
        {
            // Convert the message string to binary data.
            IBuffer buffUtf8Msg = CryptographicBuffer.ConvertStringToBinary(key, BinaryStringEncoding.Utf8);

            // Create a HashAlgorithmProvider object.
            HashAlgorithmProvider objAlgProv = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Md5);

            // Hash the message.
            IBuffer buffHash = objAlgProv.HashData(buffUtf8Msg);

            // Verify that the hash length equals the length specified for the algorithm.
            if (buffHash.Length != objAlgProv.HashLength)
            {
                throw new Exception("There was an error creating the hash");
            }

            return buffHash;
        }

        /// <summary>
        /// Encrypt a string using dual encryption method. Returns an encrypted text.
        /// </summary>
        /// <param name="toEncrypt">String to be encrypted</param>
        /// <param name="key">Unique key for encryption/decryption</param>m>
        /// <returns>Returns encrypted string.</returns>
        public static string Encrypt(string toEncrypt, string key)
        {
            try
            {
                // Get the MD5 key hash (you can as well use the binary of the key string)
                var keyHash = GetMD5Hash(key);

                // Create a buffer that contains the encoded message to be encrypted.
                var toDecryptBuffer = CryptographicBuffer.ConvertStringToBinary(toEncrypt, BinaryStringEncoding.Utf8);

                // Open a symmetric algorithm provider for the specified algorithm.
                var aes = SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithmNames.AesEcbPkcs7);

                // Create a symmetric key.
                var symetricKey = aes.CreateSymmetricKey(keyHash);

                // The input key must be securely shared between the sender of the cryptic message
                // and the recipient. The initialization vector must also be shared but does not
                // need to be shared in a secure manner. If the sender encodes a message string
                // to a buffer, the binary encoding method must also be shared with the recipient.
                var buffEncrypted = CryptographicEngine.Encrypt(symetricKey, toDecryptBuffer, null);

                // Convert the encrypted buffer to a string (for display).
                // We are using Base64 to convert bytes to string since you might get unmatched characters
                // in the encrypted buffer that we cannot convert to string with UTF8.
                var strEncrypted = CryptographicBuffer.EncodeToBase64String(buffEncrypted);

                return strEncrypted;
            }
            catch (Exception ex)
            {
                // MetroEventSource.Log.Error(ex.Message);
                return "";
            }
        }

        public static async Task<IBuffer> ConvertToBuffer(StorageFile imageFile)
        {
            var readStream = await imageFile.OpenAsync(FileAccessMode.Read);
            var inputStream = readStream.GetInputStreamAt(0);
            var dataReader = new DataReader(inputStream);

            var numBytesLoaded = await dataReader.LoadAsync((uint)readStream.Size);
            return dataReader.ReadBuffer(numBytesLoaded);

            //var numBytesLoaded = await dataReader.LoadAsync((uint)readStream.Size);
            //var byteString = new byte[numBytesLoaded];
            //dataReader.ReadBytes(byteString);
            //return Convert.ToBase64String(byteString);
        }

        public static async Task<string> ConvertBase64(StorageFile imageFile)
        {
            var readStream = await imageFile.OpenAsync(FileAccessMode.Read);
            var inputStream = readStream.GetInputStreamAt(0);
            var dataReader = new DataReader(inputStream);
           

            var numBytesLoaded = await dataReader.LoadAsync((uint)readStream.Size);
            var byteString = new byte[numBytesLoaded];
            dataReader.ReadBytes(byteString);
            return Convert.ToBase64String(byteString);
        }

        /// <summary>
        /// Decrypt a string using dual encryption method. Return a Decrypted clear string
        /// </summary>
        /// <param name="cipherString">Encrypted string</param>
        /// <param name="key">Unique key for encryption/decryption</param>
        /// <returns>Returns decrypted text.</returns>
        public static async Task<string> DecryptEpubToString(StorageFile inputFile, string key)
        {
            try
            {
                // Get the MD5 key hash (you can as well use the binary of the key string)
                var keyHash = GetMD5Hash(key);

                IBuffer toDecryptBuffer = await AESUtils.ConvertToBuffer(inputFile);

                String base64String = await AESUtils.ConvertBase64(inputFile);
                // Create a buffer that contains the encoded message to be decrypted.
                IBuffer toDecryptBuffer2 = CryptographicBuffer.DecodeFromBase64String(base64String);

                // Open a symmetric algorithm provider for the specified algorithm.
                SymmetricKeyAlgorithmProvider aes = SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithmNames.AesCbcPkcs7);

                // Create a symmetric key.
                var symetricKey = aes.CreateSymmetricKey(keyHash);

                var buffDecrypted = CryptographicEngine.Decrypt(symetricKey, toDecryptBuffer, null);

                string strDecrypted = CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8, buffDecrypted);

                return strDecrypted;
                
            }
            catch (Exception ex)
            {
                // MetroEventSource.Log.Error(ex.Message);
                //throw;
                throw new Exception(ex.Message);
            }
        }

        //public static async Task<string> DecryptEpubToString(StorageFile inputFile, string pw)
        //{

        //    try
        //    {

        //        IBuffer pwBuffer = CryptographicBuffer.ConvertStringToBinary(pw, BinaryStringEncoding.Utf8);
        //        IBuffer saltBuffer = CryptographicBuffer.ConvertStringToBinary(pw, BinaryStringEncoding.Utf8);

        //        var readStream = await inputFile.OpenAsync(FileAccessMode.Read);
        //        IInputStream inputStream = readStream.GetInputStreamAt(0);
            
        //        var dataReader = new DataReader(inputStream);
        //        await dataReader.LoadAsync((uint)readStream.Size);
        //        IBuffer cipherBuffer = dataReader.ReadBuffer((uint)readStream.Size);

        //        //IBuffer cipherBuffer = CryptographicBuffer.DecodeFromBase64String(cipherString);

        //        KeyDerivationAlgorithmProvider keyDerivationProvider = Windows.Security.Cryptography.Core.KeyDerivationAlgorithmProvider.OpenAlgorithm("PBKDF2_SHA1");

        //        KeyDerivationParameters pbkdf2Parms = KeyDerivationParameters.BuildForPbkdf2(saltBuffer, 1000);

        //        CryptographicKey keyOriginal = keyDerivationProvider.CreateKey(pwBuffer);
        //        IBuffer keyMaterial = CryptographicEngine.DeriveKeyMaterial(keyOriginal, pbkdf2Parms, 32);

        //        CryptographicKey derivedPwKey = keyDerivationProvider.CreateKey(pwBuffer);

        //        IBuffer saltMaterial = CryptographicEngine.DeriveKeyMaterial(derivedPwKey, pbkdf2Parms, 16);

        //        string keyMaterialString = CryptographicBuffer.EncodeToBase64String(keyMaterial);
        //        string saltMaterialString = CryptographicBuffer.EncodeToBase64String(saltMaterial);

        //        SymmetricKeyAlgorithmProvider symProvider = SymmetricKeyAlgorithmProvider.OpenAlgorithm("AES_CBC_PKCS7");

        //        CryptographicKey symmKey = symProvider.CreateSymmetricKey(keyMaterial);

        //        IBuffer resultBuffer = CryptographicEngine.Decrypt(symmKey, cipherBuffer, saltMaterial);

        //        byte[] asd;
        //        CryptographicBuffer.CopyToByteArray(resultBuffer, out asd);
        //        string result = CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8, resultBuffer);
        //        return result;
        //    }
        //    catch (Exception ex)
        //    {
        //        return "";
        //    }
        //}

        /// <summary>
        /// Decrypt a string using dual encryption method. Return a Decrypted clear string
        /// </summary>
        /// <param name="cipherString">Encrypted string</param>
        /// <param name="key">Unique key for encryption/decryption</param>
        /// <returns>Returns decrypted text.</returns>
        public static async Task<byte[]> DecryptEpubToBytes(string inputFile, string key)
        {
            try
            {
                // Get the MD5 key hash (you can as well use the binary of the key string)
                var keyHash = GetMD5Hash(key);

                String cipherString = await Windows.Storage.PathIO.ReadTextAsync(inputFile);

                // Create a buffer that contains the encoded message to be decrypted.
                IBuffer toDecryptBuffer = CryptographicBuffer.DecodeFromBase64String(cipherString);

                // Open a symmetric algorithm provider for the specified algorithm.
                SymmetricKeyAlgorithmProvider aes = SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithmNames.AesEcbPkcs7);

                // Create a symmetric key.
                var symetricKey = aes.CreateSymmetricKey(keyHash);

                var buffDecrypted = CryptographicEngine.Decrypt(symetricKey, toDecryptBuffer, null);

                string strDecrypted = CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8, buffDecrypted);

                byte[] byteArray = Convert.FromBase64String(strDecrypted);

                return byteArray;
            }
            catch (Exception ex)
            {
                // MetroEventSource.Log.Error(ex.Message);
                //throw;
                throw new Exception (ex.Message) ;
            }
        }
    }
}
